/* 
 * Copyright (c) 2004-2005 Massachusetts Institute of Technology. This code was
 * developed as part of the Haystack (http://haystack.lcs.mit.edu/) research 
 * project at MIT. Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files (the 
 * "Software"), to deal in the Software without restriction, including without 
 * limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following conditions: 
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software. 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE. 
 */
/*
 * Created on Feb 4, 2004
 *
 */
package edu.mit.csail.relo.ui;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.LayeredPane;
import org.eclipse.gef.ContextMenuProvider;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditDomain;
import org.eclipse.gef.KeyHandler;
import org.eclipse.gef.KeyStroke;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.palette.CombinedTemplateCreationEntry;
import org.eclipse.gef.palette.ConnectionCreationToolEntry;
import org.eclipse.gef.palette.PaletteGroup;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.palette.SelectionToolEntry;
import org.eclipse.gef.palette.ToolEntry;
import org.eclipse.gef.requests.SimpleFactory;
import org.eclipse.gef.tools.SelectionTool;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.gef.ui.actions.DirectEditAction;
import org.eclipse.gef.ui.actions.GEFActionConstants;
import org.eclipse.gef.ui.actions.ZoomInAction;
import org.eclipse.gef.ui.actions.ZoomOutAction;
import org.eclipse.gef.ui.palette.PaletteViewerProvider;
import org.eclipse.gef.ui.palette.FlyoutPaletteComposite.FlyoutPreferences;
import org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler;
import org.eclipse.gef.ui.parts.ScrollingGraphicalViewer;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.TransferDropTargetListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.openrdf.model.Literal;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.rio.ParseException;
import org.openrdf.rio.Parser;
import org.openrdf.rio.RdfDocumentWriter;
import org.openrdf.rio.StatementHandler;
import org.openrdf.rio.StatementHandlerException;
import org.openrdf.sesame.sail.StatementIterator;
import org.openrdf.vocabulary.RDF;

import edu.mit.csail.relo.ReloCore;
import edu.mit.csail.relo.ReloPlugin;
import edu.mit.csail.relo.actions.ListEditorInput;
import edu.mit.csail.relo.actions.ReloContextMenuProvider;
import edu.mit.csail.relo.agent.BrowseModel;
import edu.mit.csail.relo.commands.QueueableCommandStack;
import edu.mit.csail.relo.eclipse.gef.DefaultAfterConnectionCreationTool;
import edu.mit.csail.relo.modelBridge.Comment;
import edu.mit.csail.relo.modelBridge.NamedRel;
import edu.mit.csail.relo.modelBridge.ReloDoc;
import edu.mit.csail.relo.parts.ArtifactEditPart;
import edu.mit.csail.relo.parts.ArtifactRelEditPart;
import edu.mit.csail.relo.parts.ReloController;
import edu.mit.csail.relo.store.ReloRdfRepository;
import edu.mit.csail.relo.store.StoreUnionRepository;
import edu.mit.csail.relo.store.StoreUtil;
import edu.mit.csail.relo.utils.LoadUtils;

/**
 * @author vineet
 *
 */
public class ReloEditor extends GraphicalEditorWithFlyoutPalette {
	static final Logger logger = ReloPlugin.getLogger(ReloEditor.class);
    
    final public static String editorId = "edu.mit.csail.relo.editor";
	
	ReloDoc codeBaseItems = new ReloDoc();

	public ReloEditor() {
		DefaultEditDomain defaultEditDomain = new DefaultEditDomain(this);
		//defaultEditDomain.setActiveTool(new ConnectionCreationTool());
		defaultEditDomain.setActiveTool(new SelectionTool());
		defaultEditDomain.setCommandStack(new QueueableCommandStack());
		setEditDomain(defaultEditDomain);
	}

	public BrowseModel bm = null;
	
    // BUG-FIX: Overriding base implementation [backported from cvs]
    @Override
    protected PaletteViewerProvider createPaletteViewerProvider() {
        return new PaletteViewerProvider(getEditDomain());
    }

    /**
	 * @see org.eclipse.gef.ui.parts.GraphicalEditor#configureGraphicalViewer()
	 */
	@SuppressWarnings("deprecation")	// non-deprecated method are only available for Eclipse 3.2 +
	@Override
    protected void configureGraphicalViewer() {
		super.configureGraphicalViewer();
		final ScrollingGraphicalViewer viewer = (ScrollingGraphicalViewer)getGraphicalViewer();

		ScalableFreeformRootEditPart root = new ScalableFreeformRootEditPart() {
            @Override
            protected void createLayers(LayeredPane layeredPane) {
                super.createLayers(layeredPane);
                // enabled because needed by: ArtifactNavAidsEditPolicy
                getLayer(LayerConstants.SCALED_FEEDBACK_LAYER).setEnabled(true);
            }
        };
		//ScalableFreeformRootEditPart root = new ScalableRootEditPart();

		List<String> zoomLevels = new ArrayList<String> (3);
		zoomLevels.add(ZoomManager.FIT_ALL);
		zoomLevels.add(ZoomManager.FIT_WIDTH);
		zoomLevels.add(ZoomManager.FIT_HEIGHT);
		root.getZoomManager().setZoomLevelContributions(zoomLevels);

		IAction zoomIn = new ZoomInAction(root.getZoomManager());
		IAction zoomOut = new ZoomOutAction(root.getZoomManager());

		getActionRegistry().registerAction(zoomIn);
		getActionRegistry().registerAction(zoomOut);

		getSite().getKeyBindingService().registerAction(zoomIn);
		getSite().getKeyBindingService().registerAction(zoomOut);
		
		viewer.setRootEditPart(root);
		
		/*
		// this does not really work - should instead consider moving the origin
		GraphicalViewer gv = getGraphicalViewer();
		System.err.println(gv.getClass());
		Viewport vp = (Viewport) ((AbstractGraphicalEditPart)gv.getRootEditPart()).getFigure();
		System.err.println(vp.getHorizontalRangeModel().toString());
		System.err.println(vp.getVerticalRangeModel().toString());
		System.err.println(vp.getClass());
		System.err.println(vp.getViewLocation());
		vp.setViewLocation(50,50);
		System.err.println(vp.getViewLocation());
		*/

		ContextMenuProvider provider =
			new ReloContextMenuProvider(viewer, getActionRegistry());
		viewer.setContextMenu(provider);
		getSite().registerContextMenu("relo.editor.contextmenu", provider, viewer);
		

        // key handling
        KeyHandler keyHandler = new GraphicalViewerKeyHandler(viewer);
		keyHandler.put(KeyStroke.getPressed(SWT.F2, 0),
				getActionRegistry().getAction(GEFActionConstants.DIRECT_EDIT));
		
		viewer.setKeyHandler(keyHandler);
        
        bm.init(viewer);
	}

	/**
	 * @see org.eclipse.gef.ui.parts.GraphicalEditor#initializeGraphicalViewer()
	 */
	@Override
    protected void initializeGraphicalViewer() {
		getGraphicalViewer().setContents(codeBaseItems);
		
		TransferDropTargetListener dragSrc = new JDTTransferDropTargetListener(getGraphicalViewer());
		getGraphicalViewer().addDropTargetListener(dragSrc);

	}
	
	public ReloController getReloController() {
		return (ReloController) this.getGraphicalViewer().getEditPartRegistry().get(this.codeBaseItems);
	}

	IFile file = null;
	public IFile getFile() {
		return file;
	}

	public void setFile(IFile file) {
		this.file = file;
	}

	IProject defaultProject = null;
	
	private IFile getNewSaveFile() {
		ReloDocWizard wizard = new ReloDocWizard();
		wizard.setDefaultName(getReloController().getDefaultFileName()+".relo");
		IWorkbenchWindow ww = getSite().getWorkbenchWindow();
		IStructuredSelection ss = StructuredSelection.EMPTY;
		if (file != null)
			ss = new StructuredSelection(file);
		wizard.init(ww.getWorkbench(), ss);
		WizardDialog dialog = new WizardDialog(ww.getShell(), wizard);
		dialog.open();

		return wizard.getFile();
	}

	/* (non-Javadoc)
	 * @see edu.mit.csail.pdeConsole.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
    public void doSave(IProgressMonitor monitor) {
		if (file == null) {
			file = getNewSaveFile();
			if (file == null) {	// no new file and author asked to save, cancel 
				monitor.setCanceled(true);
				return;
			}
		}
		writeFile();
	}
	

	/* (non-Javadoc)
	 * @see edu.mit.csail.pdeConsole.part.EditorPart#doSaveAs()
	 */
	@Override
    public void doSaveAs() {
        IFile curFile = file;
        file = null;
        doSave(new NullProgressMonitor());
        
        // if it fails for some reason reset to original
        if (file == null) file = curFile;
	}

	/* (non-Javadoc)
	 * @see edu.mit.csail.pdeConsole.part.EditorPart#gotoMarker(org.eclipse.core.resources.IMarker)
	 */
	public void gotoMarker(IMarker marker) {}

	/* (non-Javadoc)
	 * @see edu.mit.csail.pdeConsole.part.EditorPart#isDirty()
	 */
	@Override
    public boolean isDirty() {
        // when no file exists => we don't allow saving, i.e. nothing is dirty
        if (file == null) return false;

        return super.isDirty();
	}

	/* (non-Javadoc)
	 * @see edu.mit.csail.pdeConsole.part.EditorPart#isSaveAsAllowed()
	 */
	@Override
    public boolean isSaveAsAllowed() {
		return true;
	}

	private IResource getSelectedResource() {
		ISelection sel = getSite().getWorkbenchWindow().getSelectionService().getSelection();
		if (sel instanceof IStructuredSelection) {
			IStructuredSelection ss = (IStructuredSelection)sel;
			Object first = ss.getFirstElement();
			if (first instanceof IResource) {
				return (IResource) first;
			} else if (first instanceof IAdaptable) {
				Object res = ((IAdaptable)first).getAdapter(IResource.class);
				if (res != null)
					return (IResource) res;
			}
		}
		
		return null;
	}

	/**
	 * @see edu.mit.csail.pdeConsole.part.EditorPart#setInput(edu.mit.csail.pdeConsole.IEditorInput)
	 */
	@Override
    protected void setInput(IEditorInput input) {
		super.setInput(input);
		
		IResource selRes = getSelectedResource();
		if (selRes != null)
			defaultProject = selRes.getProject();

		if (input instanceof ListEditorInput) {
			bm = ((ListEditorInput) input).browseModel;
			codeBaseItems.setItems(((ListEditorInput) input).list);
		} else if (input instanceof IFileEditorInput) {
			file = ((IFileEditorInput)input).getFile();
			
			ReloRdfRepository memRepo = StoreUtil.getMemRepository();
			readFile(memRepo);
			
			codeBaseItems.setRDFDoc(memRepo);
			
			bm = (BrowseModel) StoreUtil.loadClass(memRepo, ReloCore.createReloURI("root"), browseModel);
			bm.setRepo(StoreUtil.getDefaultStoreRepository());
		}
		//System.err.println("ReloEditor.setInput: " + input.getClass());
	}
	
	/**
	 * Override IWorkbenchPart.dispose() so that when a ReloEditor is closed, it is
	 * removed as a listener on the build
	 */
	@Override
	public void dispose() {
		super.dispose();
		LoadUtils.removeBuildersListener(this);
	}

	private void readFile(final ReloRdfRepository rdfRepo) {
		LoadUtils.addBuildersListener(this);
		setPartName(file.getName());
	    try {
			BufferedReader in = new BufferedReader(new InputStreamReader(file.getContents(/*force*/true)));
            Parser parser = StoreUtil.getRDFParser(rdfRepo);
			parser.setStatementHandler(new StatementHandler() {
                public void handleStatement(Resource subj, URI pred, Value obj) throws StatementHandlerException {
                    rdfRepo.addStatement(subj, pred, obj);
                }});
			rdfRepo.startTransaction();
			parser.parse(in, ReloRdfRepository.reloRdfNamespace);
			rdfRepo.commitTransaction();
	    } catch (CoreException e) {
			logger.error("Unexpected Error", e);
	    } catch (IOException e) {
			logger.error("Unexpected Error", e);
        } catch (ParseException e) {
			logger.error("Unexpected Error", e);
        } catch (StatementHandlerException e) {
			logger.error("Unexpected Error", e);
        }
	}


    public void writeFile() {
    	LoadUtils.addBuildersListener(this);
    	getReloController().writeFile(file);
		setPartName(file.getName());
//		firePropertyChange(PROP_DIRTY);
		getEditDomain().getCommandStack().markSaveLocation();
//		getEditDomain().getCommandStack().flush();
	}

	public static final URI browseModel = ReloCore.createReloURI("core#browseModel");

	/* (non-Javadoc)
	 * @see org.eclipse.gef.ui.parts.GraphicalEditor#createActions()
	 */
	@Override
    protected void createActions() {
		super.createActions();
		ActionRegistry registry = getActionRegistry();
		IAction action;

		//action = new FisheyeSemanticZoomInAction((IWorkbenchPart)this);
		//registry.registerAction(action);
		//getSelectionActions().add(action.getId());

		action = new DirectEditAction((IWorkbenchPart)this);
		registry.registerAction(action);
		addSelectionAction(action);
	}

    @SuppressWarnings("unchecked")
    private final void addSelectionAction(IAction action) {
        getSelectionActions().add(action.getId());
    }


	@Override
    public Object getAdapter(Class type){
		if (type == ZoomManager.class)
			return getGraphicalViewer().getProperty(ZoomManager.class.toString());
		else if (type == EditDomain.class)
		    return getEditDomain();

		return super.getAdapter(type);
	}

    public IFigure getContentsFigure() {
        ReloController vc = (ReloController) getGraphicalViewer().getContents();
        return vc.getFigure();
    }

	/**
	 * @see org.eclipse.gef.ui.parts.GraphicalEditorWithPalette#initializePaletteViewer()
	 */
	/*
	protected void initializePaletteViewer() {
		super.initializePaletteViewer();
		//getPaletteViewer().addDragSourceListener(
		//	new TemplateTransferDragSourceListener(getPaletteViewer()));
	}
	*/

    /* (non-Javadoc)
     * @see org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette#getPalettePreferences()
     */
    @Override
    protected FlyoutPreferences getPalettePreferences() {
        return new FlyoutPreferences() {

            int dockLocation = -1;
            public int getDockLocation() {
                return dockLocation;
            }
            public void setDockLocation(int location) {
                dockLocation = location;
            }

            int paletteState = -1;
            public int getPaletteState() {
                return paletteState;
            }
            public void setPaletteState(int state) {
                paletteState = state;
            }

            int paletteWidth = 125;
            public int getPaletteWidth() {
                return paletteWidth;
            }

            public void setPaletteWidth(int width) {
                paletteWidth = width;                
            }};
    }

	private PaletteRoot root;

	public static ImageDescriptor getImageDescriptor(String key) {
		return ImageDescriptor.createFromFile(ReloEditor.class, key);
	}
	@SuppressWarnings("unused")	// used primarily for debugging
	private final String getObjType(Object obj) {
	    return (obj==null) ? "null" : obj.getClass().toString();
	}
	
	/**
	 * Checks the editor's file for errors. If errors exist, decorate the editor's
	 * icon with an error image. If errors do not exist, remove any error decorations
	 * on the editor's image.  Finally, refresh the file's diagram.
	 *
	 */
	public void checkEditor() {
		try {
			IMarker[] markers = file.findMarkers(IMarker.PROBLEM, false, IResource.DEPTH_ZERO);
			if(markers.length > 0) {
				addErrorDecoration();
			} else {
				removeErrorDecoration();
			}
			Shell shell = getEditorSite().getShell();
			shell.getDisplay().asyncExec(new Runnable() {
				public void run() {
					getReloController().refresh();
				}}); 
		} catch (CoreException e) {
			logger.error("Could not find associated file's error markers. ", e);
		}
	}
	
	/**
	 * Decorates the title image of this editor with an error decoration.
	 * 
	 */
	public void addErrorDecoration() {
		ImageDescriptor editorImage= ImageDescriptor.createFromImage(getTitleImage());
		final ImageDescriptor errorImage = ImageDescriptor.createFromFile(ReloEditor.class, "error_co.gif");
		ReloImageDecorator decorator = new ReloImageDecorator(editorImage, errorImage, ReloImageDecorator.BOTTOM_LEFT);
		final Image decoratedImage = decorator.createImage();
		Shell shell = getEditorSite().getShell();
		shell.getDisplay().asyncExec(new Runnable() {
			public void run() {
				setTitleImage(decoratedImage);
			}});
	}
	
	/**
	 * Removes any error decoration on the title image of this editor
	 *
	 */
	public void removeErrorDecoration() {
		URL url = ReloPlugin.getDefault().getBundle().getEntry("icons/relo-document.png");
		try {
			final Image errorFreeImage = new Image(Display.getDefault(), url.openStream());
			Shell shell = getEditorSite().getShell();
			shell.getDisplay().asyncExec(new Runnable() {
				public void run() {
					setTitleImage(errorFreeImage);
				}});
		} catch (IOException e) {
			logger.error("Could not remove error decoration. ", e);
		}
	}
	
    /* (non-Javadoc)
	 * @see org.eclipse.gef.ui.parts.GraphicalEditorWithPalette#getPaletteRoot()
	 */
	@Override
    protected PaletteRoot getPaletteRoot() {
		if (root == null) {
			root = new PaletteRoot();

			PaletteGroup controlGroup = new PaletteGroup("Control Group");

			ToolEntry tool = new SelectionToolEntry();
			controlGroup.add(tool);
			root.setDefaultEntry(tool);

			//tool = new MarqueeToolEntry();
			//controlGroup.add(tool);
			
			
			ImageDescriptor img;

            img = getImageDescriptor("comment.gif");
			controlGroup.add(new CombinedTemplateCreationEntry(
                    "Add Comment", 
					"Can be used to add comments and other notes to the diagram", 
                    new SimpleFactory(Comment.class),
                    new SimpleFactory(Comment.class), 
                    img, 
                    img));
			

			img = getImageDescriptor("link.gif");
            ToolEntry ccte = new ConnectionCreationToolEntry(
                    "Add Relationship", 
                    "Create a named link",
                    new SimpleFactory(NamedRel.class), 
                    img, img);
            ccte.setToolClass(DefaultAfterConnectionCreationTool.class);
			controlGroup.add(ccte);
			

			//controlGroup.add(new PaletteSeparator());

			
			root.add(controlGroup);
		}
		return root;
	}

	public void init(IViewSite site) throws PartInitException {
        setSite(site);
    	setInput(null);
    	getCommandStack().addCommandStackListener(this);
    	getSite().getWorkbenchWindow().getSelectionService().addSelectionListener(this);
    	initializeActionRegistry();
	}

}
